package cn.xzc.job.type.cron;

import cn.xzc.job.util.StringUtils;

import java.time.DateTimeException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.ValueRange;
import java.util.function.BiFunction;

/**
 * Single field in a cron pattern. Created using the parse* methods, main and only entry point is nextOrSame(Temporal).
 * cron模式中的单个字段。使用parse*方法创建，主要且唯一的入口点是 nextOrName（Temporal）
 *
 * @author xzc
 * 当前时间 2024-02-12 21:30:00
 */
abstract class CronField {

    /**
     * 月份简称
     */
    private static final String[] MONTHS = new String[]{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP",
            "OCT", "NOV", "DEC"};

    /**
     * 周一-周日的英文简称
     */
    private static final String[] DAYS = new String[]{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};

    /**
     * cron field type: seconds, minutes, hours, days of month, months, days of week.
     * 定时任务的字段类型：秒、分钟、小时、天、一个月中的某一天、一周中的周几
     */
    private final Type type;


    protected CronField(Type type) {
        this.type = type;
    }

    /**
     * Return a {@code CronField} enabled for 0 nano seconds.
     */
    public static CronField zeroNanos() {
        return BitsCronField.zeroNanos();
    }

    /**
     * Parse the given value into a seconds {@code CronField}, the first entry of a cron expression.
     */
    public static CronField parseSeconds(String value) {
        return BitsCronField.parseSeconds(value);
    }

    /**
     * Parse the given value into a minutes {@code CronField}, the second entry of a cron expression.
     */
    public static CronField parseMinutes(String value) {
        return BitsCronField.parseMinutes(value);
    }

    /**
     * Parse the given value into a hours {@code CronField}, the third entry of a cron expression.
     */
    public static CronField parseHours(String value) {
        return BitsCronField.parseHours(value);
    }

    /**
     * Parse the given value into a days of months {@code CronField}, the fourth entry of a cron expression.
     */
    public static CronField parseDaysOfMonth(String value) {
        if (!QuartzCronField.isQuartzDaysOfMonthField(value)) {
            return BitsCronField.parseDaysOfMonth(value);
        } else {
            return parseList(value, Type.DAY_OF_MONTH, (field, type) -> {
                if (QuartzCronField.isQuartzDaysOfMonthField(field)) {
                    return QuartzCronField.parseDaysOfMonth(field);
                } else {
                    return BitsCronField.parseDaysOfMonth(field);
                }
            });
        }
    }

    /**
     * Parse the given value into a month {@code CronField}, the fifth entry of a cron expression.
     */
    public static CronField parseMonth(String value) {
        value = replaceOrdinals(value, MONTHS);
        return BitsCronField.parseMonth(value);
    }

    /**
     * Parse the given value into a days of week {@code CronField}, the sixth entry of a cron expression.
     */
    public static CronField parseDaysOfWeek(String value) {
        value = replaceOrdinals(value, DAYS);
        if (!QuartzCronField.isQuartzDaysOfWeekField(value)) {
            return BitsCronField.parseDaysOfWeek(value);
        } else {
            return parseList(value, Type.DAY_OF_WEEK, (field, type) -> {
                if (QuartzCronField.isQuartzDaysOfWeekField(field)) {
                    return QuartzCronField.parseDaysOfWeek(field);
                } else {
                    return BitsCronField.parseDaysOfWeek(field);
                }
            });
        }
    }


    private static CronField parseList(String value, Type type, BiFunction<String, Type, CronField> parseFieldFunction) {
        String[] fields = StringUtils.delimitedListToStringArray(value, ",");
        CronField[] cronFields = new CronField[fields.length];
        for (int i = 0; i < fields.length; i++) {
            cronFields[i] = parseFieldFunction.apply(fields[i], type);
        }
        return CompositeCronField.compose(cronFields, type, value);
    }

    private static String replaceOrdinals(String value, String[] list) {
        value = value.toUpperCase();
        for (int i = 0; i < list.length; i++) {
            String replacement = Integer.toString(i + 1);
            value = StringUtils.replace(value, list[i], replacement);
        }
        return value;
    }

    @SuppressWarnings("unchecked")
    protected static <T extends Temporal & Comparable<? super T>> T cast(Temporal temporal) {
        return (T) temporal;
    }

    /**
     * Get the next or same {@link Temporal} in the sequence matching this
     * cron field.
     *
     * @param temporal the seed value
     * @return the next or same temporal matching the pattern
     */
    
    public abstract <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal);

    protected Type type() {
        return this.type;
    }


    /**
     * Represents the type of cron field, i.e. seconds, minutes, hours,
     * day-of-month, month, day-of-week.
     * 定时任务的字段类型：秒、分钟、小时、天、一个月中的某一天、一周中的周几
     */
    protected enum Type {
        /**
         * nano 纳秒
         * SECOND 秒
         * MINUTE 分钟
         * HOUR 小时
         * DAY_OF_MONTH 一个月中的某一天
         * MONTH 月
         * DAY_OF_WEEK 一周中的周几
         */
        NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS),
        SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND),
        MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
        HOUR(ChronoField.HOUR_OF_DAY, ChronoUnit.DAYS, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
        DAY_OF_MONTH(ChronoField.DAY_OF_MONTH, ChronoUnit.MONTHS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
        MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
        DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);


        private final ChronoField field;

        private final ChronoUnit higherOrder;
        /**
         * field对应的时间类型的所有下级时间字段
         * 比如：分对应的下级时间字段就是纳秒和秒
         */
        private final ChronoField[] lowerOrders;


        Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) {
            this.field = field;
            this.higherOrder = higherOrder;
            this.lowerOrders = lowerOrders;
        }


        /**
         * Return the value of this type for the given temporal.
         * 根据date字段类型获取相应的值
         *
         * @return the value of this type
         */
        public int get(Temporal date) {
            return date.get(this.field);
        }

        /**
         * Return the general range of this type.
         * 返回对应时间类型字段的有效时间区间范围。【比如；类型为月时，范围就是1-31】
         *
         * @return the range of this field
         */
        public ValueRange range() {
            return this.field.range();
        }

        /**
         * Check whether the given value is valid, i.e. whether it falls in range.
         * 校验值是否是当前时间字段类型的范围有效区间
         *
         * @param value the value to check
         * @return the value that was passed in
         * @throws IllegalArgumentException if the given value is invalid
         */
        public int checkValidValue(int value) {
            if (this == DAY_OF_WEEK && value == 0) {
                return value;
            } else {
                try {
                    return this.field.checkValidIntValue(value);
                } catch (DateTimeException ex) {
                    throw new IllegalArgumentException(ex.getMessage(), ex);
                }
            }
        }

        /**
         * Elapse the given temporal for the difference between the current value of this field and the goal value.
         * Typically, the returned temporal will have the given goal as the current value for this type, but this is not the case for {@link #DAY_OF_MONTH}.
         *
         * @param temporal the temporal to elapse
         * @param goal     the goal value 目标值
         * @param <T>      the type of temporal
         * @return the elapsed temporal, typically with {@code goal} as value for this type.
         */
        public <T extends Temporal & Comparable<? super T>> T elapseUntil(T temporal, int goal) {
            int current = get(temporal);
            ValueRange range = temporal.range(this.field);
            //当对应时间类型的值小于目标值时
            if (current < goal) {
                //当目标值在有效时间范围内
                if (range.isValidIntValue(goal)) {
                    return cast(temporal.with(this.field, goal));
                } else {
                    // goal is invalid, eg. 29th Feb, so roll forward  目标值不在有效范围【大于有效范围区间】
                    long amount = range.getMaximum() - current + 1;
                    return this.field.getBaseUnit().addTo(temporal, amount);
                }
            } else {
                //目标值小于当前时间类型对应的值时:
                long amount = goal + range.getMaximum() - current + 1 - range.getMinimum();
                return this.field.getBaseUnit().addTo(temporal, amount);
            }
        }

        /**
         * Roll forward the give temporal until it reaches the next higher order field.
         * Calling this method is equivalent to calling {@link #elapseUntil(Temporal, int)} with goal set to the minimum value of this field's range.
         * 对应时间类型上级时间类型+1，当前时间类型重置成最小值
         *
         * @param temporal the temporal to roll forward
         * @param <T>      the type of temporal
         * @return the rolled forward temporal
         */
        public <T extends Temporal & Comparable<? super T>> T rollForward(T temporal) {
            //给对应时间字段类型大一级的时间类型+1
            T result = this.higherOrder.addTo(temporal, 1);
            //给对应时间类型重置成最小值
            ValueRange range = result.range(this.field);
            return this.field.adjustInto(result, range.getMinimum());
        }

        /**
         * Reset this and all lower order fields of the given temporal to their minimum value.
         * For instance for {@link #MINUTE}, this method resets nanos, seconds, <strong>and</strong> minutes to 0.
         * 将对应字段的下级时间字段重置成最小值。【重置该字段的下级时间字段】
         * 比如：字段是月时，就会将纳秒=0，秒=0，分=0、时=0、日=1
         *
         * @param temporal the temporal to reset 需要重置的日期时间对象
         * @param <T>      the type of temporal
         * @return the reset temporal 重置后的temporal
         */
        public <T extends Temporal> T reset(T temporal) {
            for (ChronoField lowerOrder : this.lowerOrders) {
                //temporal中是否包含该下级时间字段
                if (temporal.isSupported(lowerOrder)) {
                    temporal = lowerOrder.adjustInto(temporal, temporal.range(lowerOrder).getMinimum());
                }
            }
            return temporal;
        }

        @Override
        public String toString() {
            return this.field.toString();
        }
    }

}
